package at.chrenko.tu.vs.manager;

import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

import at.chrenko.tu.vs.rmi.NotificationRMI;
import at.chrenko.tu.vs.shared.FileTask;
import at.chrenko.tu.vs.shared.TaskState;

public class ExecutionHandler extends Thread {

	private FileTask task;
	private NotificationRMI callback;
	private Company company;
	private String message;
	
	public String getMessage() {
		return message;
	}
	
	public ExecutionHandler(FileTask task, NotificationRMI callback, Company company) {
		this.task = task;
		this.callback = callback;
		this.company = company;
	}
	
	@Override
	public void run() {
		
		ExecutorService threadPool = Executors.newFixedThreadPool(task.getAmount());
				
		List<ExecutionWorker> callables = new ArrayList<ExecutionWorker>();
		
		for(int i=0; i < task.getAmount(); i++) {
			callables.add(new ExecutionWorker(task, company, i));
		}
		
		List<Future<Boolean>> result = new ArrayList<Future<Boolean>>();
		
		task.setTaskState(TaskState.EXECUTING);
		message = "Execution for task " + task.getId() + " started.";
		notifySchedulerWorker();
		
		try {
			result = threadPool.invokeAll(callables);
		} catch (InterruptedException e) {
			errorOccurred();
			return;
		}
		
		for(Future<Boolean> f : result) {
			try {
				if(f.get()) {
					errorOccurred();
					return;
				}
			} catch (InterruptedException e) {
				errorOccurred();
				return;
			} catch (ExecutionException e) {
				errorOccurred();
				return;
			}
		}
		
		company.getLock().lock();
		
		task.setTaskState(TaskState.FINISHED);
		
		for(ExecutionWorker exWorker : callables) {
			task.setCosts(task.getCosts() + exWorker.getCosts());
			task.appendOutput(exWorker.getOutput());
		}
		
		if(task.getCosts() <= company.getCredits()) {
			company.addCredits(-task.getCosts());
			task.setPaid(true);
		}
		
		switch(task.getTaskType()) {
			case LOW:
				company.incrementCountLow();
				break;
			case MIDDLE:
				company.incrementCountMiddle();
				break;
			case HIGH:
				company.incrementCountHigh();
				break;
			default:
				break;
		}
		
		notifyCallback("Execution of task " + task.getId() + " finished. (Costs: " + task.getCosts() + ")");
		
		company.getLock().unlock();
		
		threadPool.shutdown();
	}
	
	private void notifySchedulerWorker() {
		synchronized(this){
			notify();
		}
	}
	
	private void notifyCallback(String message) {
		try {
			callback.notify(message);
		} catch (RemoteException e) {
			System.err.println("Error notifying the client!");
		}
	}
	
	private void errorOccurred() {
		task.setTaskState(TaskState.PREPARED);
		notifyCallback("Error: An error occured while executing the task!\nSet state of Task back to PREPARED!");
	}
}
